/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package model;

import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.TableCellRenderer;

import org.json.simple.JSONObject;
import org.json.simple.JSONValue;

import view.PlayerCanvas;
import view.contents.ImageBox;
import view.contents.RecordContainer;
import view.contents.RecordList;
import view.contents.TableBox;
import view.contents.TextLabelBox;
import config.Debug;
import config.Helper;
import config.Settings;

/**
 * 
 * @author Yang Wang <yang@digitalmenubox.com>
 */
public class InventoryManager {

	private static InventoryManager _instance = new InventoryManager();
	// string constants from database table: inventory_definition
	public static final String BOX_TYPE = "boxType"; // used to detect what kind of box it this player: BOX_TYPE_AUTOBOX OR BOX_TYPE_REALESTATEBOX
	public static final String BOX_TYPE_AUTOBOX = "autobox";
	public static final String BOX_TYPE_REALESTATEBOX = "realestatebox";
	public static final String TYPE_MAPPING = "mapping";
	public static final String TYPE_IMAGE = "image";
	public static final String TYPE_MAIN_IMAGE_CONTAINER = "mainImageContainer";
	public static final String TYPE_TABLE = "table";
	public static final String TYPE_TABLE_CELL_FORMAT = "tableCellFormat";
	public static final String TYPE_TABLE_HEADER_FORMAT = "tableHeaderFormat";
	public static final String TYPE_TABLE_NUM_ROWS = "tableNumberOfRows";
	public static final String TYPE_BATCH_SIZE = "batchSize";
	public static final String TYPE_PAUSE_TIME = "pauseTime";
	public static final String TYPE_TABLE_FIELDS = "tableFields";
	public static final String TYPE_TABLE_FIELDS_FORMAT = "table_field";
	public static final String TYPE_TABLE_ROW_HEIGHT = "tableRowHeight";
	public static final String TYPE_TABLE_HIGHLIGHT = "tableHightlightFormat";
	public static final String TYPE_REALESTATEBOX_RECORD_SPACING = "realEstateBoxRecordSpacing";

	private int numInventoryImages = 0;
	private String[] fields = new String[] { "carID", "company", "stocknumber", "vin", "year", "make", "model", "mileage", "exterior", "interior", "engine",
			"price", "option1", "option2", "manufacturerprogram", "picture1", "picture2", "picture3", "picture4", "gamedate", "gametime", "z_inventoryid_fk",
			"dateadded", "new_inventory", "price_changed", "is_expired", "date_sold", "date_price_changed", "z_vehicleid_pk", "price_change_times",
			"co_agent_id", "street", "city", "province", "country", "zip" };
	
	private List<InventoryItem> items = new ArrayList<InventoryItem>();
	
	// a content type to container mapping depending on what content it is
	private Map<String, Integer> map = new HashMap<String, Integer>();
	
	// a content type to format mapping
	private Map<String, String> format = new HashMap<String, String>();

	// a content type to container mapping depending on what content it is
	private Map<String, Integer> imageMap = new HashMap<String, Integer>();

	// stores cell format for each table column
	private Map<String, String> TableFieldMap = new HashMap<String, String>();
	
	private int mainImageContainerId = 0;
	private int tableContainerId;
	private int pauseTime = 2;
	private int batchSize = 5;
	private int position = 0;
	private int numTableRows = 5;
	private String tableCellFormat;
	private String tableHeaderFormat;
	private String tableFields;
	private int tableRowHeight = 40;
	private String tableHightlightFormat;
	private String boxType = BOX_TYPE_AUTOBOX;
	private int record_spacing = 10; // real estate box vertical record spacing

	private InventoryManager() {
		this.fetchInventoryMapping();
		this.fetchInventoryItems();
	}

	public void update() {
		items = new ArrayList<InventoryItem>();
		map = new HashMap<String, Integer>();
		format = new HashMap<String, String>();
		this.fetchInventoryMapping();
		this.fetchInventoryItems();
	}

	public static InventoryManager getInstance() {
		return _instance;
	}

	public boolean hasInventory() {
		return (this.items.size() > 0 && this.map.size() > 0);
	}

	public void fetchInventoryMapping() {
		String sql = String.format("SELECT * FROM `inventory_definition`");
		Connection conn = Settings.getLocalDatabaseConnection();
		ResultSet result = null;
		Statement stmt = null;
		try {
			int numResults = Helper.getNumberOfResults(sql);
			if (numResults <= 0) {
				// this is not an autobox/realestatebox since it does not have
				// inventory mapping
				return;
			}

			// fetch the records one by one
			stmt = conn.createStatement();
			result = stmt.executeQuery(sql);
			result.beforeFirst();
			while (result.next()) {
				String type = result.getString("type");
				if (type.equalsIgnoreCase(TYPE_MAPPING)) {
					map.put(result.getString("field"), result.getInt("container_id"));
					format.put(result.getString("field"), result.getString("format"));
				} else if (type.equalsIgnoreCase(TYPE_IMAGE)) {
					imageMap.put(result.getString("field"), result.getInt("container_id"));
				} else if (type.equalsIgnoreCase(TYPE_MAIN_IMAGE_CONTAINER)) {
					this.mainImageContainerId = result.getInt("container_id");
				} else if (type.equalsIgnoreCase(TYPE_TABLE)) {
					this.tableContainerId = result.getInt("container_id");
					this.tableFields = result.getString("field");
				} else if (type.equalsIgnoreCase(TYPE_TABLE_CELL_FORMAT)) {
					this.tableCellFormat = result.getString("format");
				} else if (type.equalsIgnoreCase(TYPE_TABLE_HEADER_FORMAT)) {
					this.tableHeaderFormat = result.getString("format");
				} else if (type.equalsIgnoreCase(TYPE_BATCH_SIZE)) {
					this.batchSize = result.getInt("format");
				} else if (type.equalsIgnoreCase(TYPE_PAUSE_TIME)) {
					this.pauseTime = result.getInt("format");
				} else if (type.equalsIgnoreCase(TYPE_TABLE_NUM_ROWS)) {
					this.numTableRows = result.getInt("format");
				} else if (type.equalsIgnoreCase(TYPE_TABLE_FIELDS)) {
					this.tableFields = result.getString("field");
				} else if (type.equalsIgnoreCase(TYPE_TABLE_FIELDS_FORMAT)) {
					this.TableFieldMap.put(result.getString("field"), result.getString("format"));
				} else if (type.equalsIgnoreCase(TYPE_TABLE_ROW_HEIGHT)) {
					this.tableRowHeight = result.getInt("format");
				} else if (type.equalsIgnoreCase(TYPE_TABLE_HIGHLIGHT)) {
					this.tableHightlightFormat = result.getString("format");
				} else if (type.equalsIgnoreCase(BOX_TYPE)) {
					this.boxType = result.getString("format");
				} else if (type.equalsIgnoreCase(TYPE_REALESTATEBOX_RECORD_SPACING)) {
					this.record_spacing = result.getInt("format");
				}

			}
		} catch (SQLException ex) {
			Debug.log("Invalid data format while reading inventory definition", Debug.ERROR_LEVEL);
		} finally {
			// close mysql result and statement
			try {
				if (result != null) {
					result.close();
				}
				if (stmt != null) {
					stmt.close();
				}
			} catch (SQLException ex) {
				Debug.log(ex.getMessage(), Debug.EXCEPTION_LEVEL);
			}
		}
	}

	/*
	 * initializer fetch and store all inventory items from database
	 */
	public void fetchInventoryItems() {
		String sql = String.format("SELECT * FROM `inventory`");
		Connection conn = Settings.getLocalDatabaseConnection();
		ResultSet result = null;
		Statement stmt = null;
		try {
			int numResults = Helper.getNumberOfResults(sql);
			if (numResults <= 0) {
				// this is not an autobox/realestatebox since it does not have
				// inventory mapping
				return;
			}
			stmt = conn.createStatement();
			result = stmt.executeQuery(sql);
			result.beforeFirst();
			while (result.next()) {
				InventoryItem item = new InventoryItem();
				for (String field : fields) {
					try {
						item.setFieldData(field, result.getString(field));
					} catch (SQLException ex) {
						item.setFieldData(field, null);
					}
				}
				this.items.add(item);
			}
		} catch (SQLException ex) {
			// send log to server when an exception occurs
			Debug.log(InventoryManager.class.getName() + ": SQLException: " + ex.getMessage(), Debug.EXCEPTION_LEVEL);
		} finally {
			// close mysql result and statement
			try {
				if (result != null) {
					result.close();
				}
				if (stmt != null) {
					stmt.close();
				}
			} catch (SQLException ex) {
				// send log to server when an exception occurs
				Debug.log(InventoryManager.class.getName() + ": SQLException: " + ex.getMessage(), Debug.EXCEPTION_LEVEL);
			}
		}
	}

	/*
	 * play inventory items depending on the batch size
	 */
	public void play() throws InterruptedException {
		int count = 0;
		while (count < this.batchSize) {
			this.playInventory();
			count++;
		}
	}

	// precondition: none
	// postcondition: play a inventory if there is one to display
	public void playInventory() throws InterruptedException {
		if (this.map.isEmpty() || this.items.size() == 0) {
			// nothing to play
			System.out.println("nothing to play");
			return;
		}

		// get the current inventory item that needs to be displayed
		final InventoryItem item = this.items.get(this.position);
		// reset number of inventory images
		this.numInventoryImages = this.imageMap.size();
		for (final Object field : this.map.keySet()) {
			// create a playable object that can be played by the player
			Playable p = new Playable() {

				@Override
				public int getContainerId() {
					return InventoryManager.this.map.get(field);
				}

				@Override
				public Content getContent() {
					// treat as text label
					// System.out.println(InventoryManager.this.format.get(field));
					JSONObject obj = (JSONObject) JSONValue.parse(InventoryManager.this.format.get(field));
					obj.put("text", item.getFieldData(field.toString()));
					return new Content(obj.toString(), ContentType.TEXT);
				}
			};

			if (p.getContent() != null && Container.containerExists(p.getContainerId())) {
				PlayerCanvas.getInstance().play(p);
			}

			// System.out.println(p.getContent());
			// System.out.println(p.getContainerId());
			// if there is no content to display for picturem, then skip this
			// step
		}

		for (final Object field : this.imageMap.keySet()) {
			// create a playable object that can be played by the player
			Playable p = new Playable() {

				@Override
				public int getContainerId() {
					return InventoryManager.this.imageMap.get(field);
				}

				@Override
				public Content getContent() {
					Object fieldData = item.getFieldData(field.toString());
					if (fieldData != null && fieldData.toString().length() > 0) {
						// return new
						// Content(item.getFieldData(field.toString()),
						// ContentType.IMAGE)
						return new Content(Integer.parseInt(fieldData.toString()));
					} else {
						return null;
					}
				}
			};

			if (p.getContent() != null && Container.containerExists(p.getContainerId())) {
				PlayerCanvas.getInstance().play(p);
			}

			// System.out.println(p.getContent());
			// System.out.println(p.getContainerId());
			// if there is no content to display for picture, then skip this
			// step
		}

		if (this.boxType.equalsIgnoreCase(BOX_TYPE_AUTOBOX)) {
			playTable();
		} else {
			playRealestateList();
		}
		// playRealestateList();
		PlayerCanvas.getInstance().invalidate();
		PlayerCanvas.getInstance().repaint();

		// now the inventory images will be rotated until all pictures are
		// displayed once on the main image area
		for (final String field : imageMap.keySet()) {
			Playable p = new Playable() {

				@Override
				public int getContainerId() {
					return InventoryManager.this.mainImageContainerId;
				}

				@Override
				public Content getContent() {
					// treat this as image content
					Object fieldData = item.getFieldData(field);

					if (fieldData != null && fieldData.toString().length() > 0) {
						return new Content(Integer.parseInt(fieldData.toString()));
					} else {
						return null;
					}
				}
			};

			if (p.getContent() != null && Container.containerExists(p.getContainerId())) {
				PlayerCanvas.getInstance().play(p);
				PlayerCanvas.getInstance().getpLayout().revalidate();
				PlayerCanvas.getInstance().getpLayout().repaint();
			}

			if (Container.containerExists(p.getContainerId())) {
				// Thread.sleep(200);
				Thread.sleep(this.pauseTime * 1000);
			}
			// System.out.println("waiting for " + this.pauseTime);
		}

		if (this.numInventoryImages == 0) {
			Thread.sleep(this.pauseTime);
		}

		this.position = (this.position == this.items.size() - 1) ? 0 : ++this.position;
	}

	public void playRealestateList() {
		if(this.tableContainerId <= 0 || this.tableFields == null) {
			Debug.log("RealestateBox display real estatebox listing: no inventory list container or no fields defined.", Debug.ERROR_LEVEL);
			return;
		}
		final RecordList rl = new RecordList(this.numTableRows, this.record_spacing, this.tableContainerId);
		for (int i = this.position + 1; i < this.items.size() && i <= this.position + this.numTableRows; i++) {
			TextLabelBox price = null;
			TextLabelBox address = null;
			TextLabelBox city = null;
			ImageBox recordPicture = null;
			if (this.items.get(i).getFieldData("price") != null) {
				JSONObject obj = new JSONObject();
				obj.put("prefix", "$");
				obj.put("font-size", "16");
				obj.put("text", this.items.get(i).getFieldData("price"));
				price = new TextLabelBox(obj.toString(), true);
			}
			if (this.items.get(i).getFieldData("city") != null) {
				JSONObject obj = new JSONObject();
				obj.put("text", this.items.get(i).getFieldData("city"));
				obj.put("font-size", "16");
				city = new TextLabelBox(obj.toString(), true);
			}
			if (this.items.get(i).getFieldData("make") != null) {
				JSONObject obj = new JSONObject();
				obj.put("text", this.items.get(i).getFieldData("make").toString() + ", " + this.items.get(i).getFieldData("city").toString());
				obj.put("font-size", "16");
				address = new TextLabelBox(obj.toString(), true);
			}

			Container tableContainer =  new Container(this.tableContainerId);
			int unitWidth = tableContainer.getWidth();
			int unitHeight = tableContainer.getHeight()/this.numTableRows;
			
			recordPicture = new ImageBox(new Content(Integer.parseInt(this.items.get(i).getFieldData("picture1").toString())), unitWidth, unitHeight-20);
			RecordContainer rc = new RecordContainer(recordPicture, address, city, price, unitWidth, unitHeight);
			rl.addRecord(rc);
		}
		
		SwingUtilities.invokeLater(new Runnable() {

			@Override
			public void run() {
				PlayerCanvas.getInstance().play(rl, InventoryManager.this.tableContainerId);

			}

		});

	}

	public void playTable() {
		// now put the table in
		if (this.tableContainerId > 0 && this.tableFields != null) {
			Container container = new Container(this.tableContainerId);
			String[] fields = tableFields.replaceAll(" ", "").split(",");
			Object[][] data = new Object[this.numTableRows][fields.length];
			for (int i = this.position - this.position % this.numTableRows; i < this.position - this.position % this.numTableRows + this.numTableRows; i++) {
				for (int j = 0; j < fields.length; j++) {
					// make sure we array is not out of bound

					if (i >= this.items.size()) {
						break;
					}
					String cellFormat = this.TableFieldMap.get(fields[j]);
					JSONObject obj = null;
					if (cellFormat != null) {
						obj = (JSONObject) JSONValue.parse(cellFormat);
						obj.put("text", this.items.get(i).getFieldData(fields[j]));
					} else {
						obj = new JSONObject();
						obj.put("text", this.items.get(i).getFieldData(fields[j]));
					}
					int rowNumber = this.position % this.numTableRows;
					if (i == rowNumber) {
						if (this.tableHightlightFormat != null) {
							JSONObject format = (JSONObject) JSONValue.parse(tableHightlightFormat);
							if (format != null) {

							}
							if (format.get("background-color") != null) {
								obj.put("background-color", format.get("background-color").toString());
							}
							if (format.get("background-color") != null) {
								obj.put("color", format.get("color").toString());
							}
						}
					}
					data[i % this.numTableRows][j] = obj;
				}
			}

			/*
			 * for (int i = 0; i < fields.length; i++) { String str = fields[i];
			 * fields[i] = str.toUpperCase(); }
			 */

			final TableBox box = new TableBox(data, fields, container.getWidth(), container.getHeight());
			box.setRowHeight(this.tableRowHeight);
			box.setRenderer(new TransparentBackgroundRenderer(this.tableCellFormat));
			for (int i = 0; i < fields.length; i++) {
				String cellFormat = this.TableFieldMap.get(fields[i]);
				if (cellFormat != null) {
					JSONObject obj = (JSONObject) JSONValue.parse(cellFormat);
					if (obj.get("width") != null) {
						try {
							int width = Integer.parseInt(obj.get("width").toString());
							box.setColumnWidth(i, width);
						} catch (NumberFormatException ex) {
							Debug.log("Invalid table column width for column " + fields[i], Debug.ERROR_LEVEL);
							Debug.msg("Invalid table column width for column " + fields[i]);
						}
					}
				} else {
					// System.out.println(cellFormat+ i);
				}

			}
			// box.setHeaderRenderer(new
			// TransparentBackgroundRenderer(this.tableHeaderFormat));
			PlayerCanvas.getInstance().play(box, tableContainerId);
		}
	}

	public static void main(String[] args) throws InterruptedException {
		// InventoryManager im = InventoryManager.getInstance();
		// im.playInventory();
		// JSONObject obj = (JSONObject)
		// JSONValue.parse("{\"font\":\"Arial\",\"font-size\":\"14\",color\":\"000000\",\"font-align\":\"Left\"}");
		// System.out.println(obj);

	}

	// A TableCellRenderer that has a transparent background
	class TransparentBackgroundRenderer extends JLabel implements TableCellRenderer {

		private String jsonString;

		public TransparentBackgroundRenderer(String jsonString) {
			super();
			this.jsonString = jsonString;
			this.setLabelProperties(this, jsonString);
		}

		private JLabel[] convertStringsToLabels(Object[] strings, String jsonString) {
			if (jsonString == null) {
				return null;
			}

			JLabel[] labels = new JLabel[strings.length];
			for (int i = 0; i < strings.length; i++) {
				// set label[i] to be a new JLabel with the text of strings[i]
				labels[i] = new JLabel(strings[i].toString());
				this.setLabelProperties(labels[i], jsonString);
			}
			return labels;
		}

		private void setLabelProperties(JLabel label, String jsonString) {
			if (jsonString == null) {
				return;
			}
			JSONObject obj = (JSONObject) JSONValue.parse(jsonString);
			String text = obj.get("text") == null ? "" : obj.get("text").toString();
			String font = obj.get("font") == null ? "Arial" : obj.get("font").toString();
			String fontSize = obj.get("font-size") == null ? "14" : obj.get("font-size").toString();
			int style = obj.get("font-style") == null ? Font.PLAIN : Font.ITALIC;
			style = obj.get("font-weight") == null ? style : style | Font.BOLD;
			String fontColor = obj.get("color") == null ? "000000" : obj.get("color").toString();

			float textAlign = JLabel.LEFT_ALIGNMENT;
			if (obj.get("font-align") != null) {
				if (obj.get("font-align").toString().equalsIgnoreCase("right")) {
					textAlign = JLabel.RIGHT_ALIGNMENT;
				} else if (obj.get("font-align").toString().equalsIgnoreCase("center")) {
					textAlign = JLabel.CENTER_ALIGNMENT;
				}
			}
			this.setAlignmentX(textAlign);

			// set fonts
			this.setLabelFont(label, font, fontSize, style);
			// set text label color
			this.setLabelColor(label, fontColor);
		}

		// precondition: label is not null, fontName is a valid font name that
		// postcondition: label has new font
		private void setLabelFont(JLabel label, String fontName, String strFontSize, int style) {
			if (null != fontName) {
				int fontStyle = style;
				int fontSize = (strFontSize != null) ? Integer.parseInt(strFontSize) : 14;
				Font font = new Font(fontName, style, fontSize);
				label.setFont(font);
			}
		}

		// precondition: label is not null
		// postcondition: label text is set if format is not null
		private void setLabelText(JLabel label, String format, String prefix, String postfix) {
			if (null != format) {
				label.setText(prefix + format.toString() + postfix);
			}
		}

		// precondition: format is a HEX string with format : 'DEADBEEF'
		// postcondition:
		private void setLabelColor(JLabel label, String format) {
			if (null != format) {
				Color color = Color.black;
				String colorString = format;
				if (colorString.length() == 6) {
					try {
						// takes 2 characters each time to determine the RGB
						// color
						color = new Color(Integer.parseInt(colorString.substring(0, 2), 16), Integer.parseInt(colorString.substring(2, 4), 16),
								Integer.parseInt(colorString.substring(4, 6), 16));
					} catch (NumberFormatException ex) {
						// illegal font-color is set in the input, the label
						// will use default text color: black
						Debug.log("illegal font-color format for text-label", Debug.ERROR_LEVEL);
					}
				}
				label.setForeground(color);
			}
		}

		@Override
		public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
			if (value != null) {
				this.setBorder(null);
				JSONObject obj = (JSONObject) value;
				this.setLabelProperties(this, obj.toString());
				String prefix = obj.get("prefix") == null ? "" : obj.get("prefix").toString();
				String postfix = obj.get("postfix") == null ? "" : obj.get("postfix").toString();
				this.setText(prefix + obj.get("text") + postfix);
				if (obj.get("background-color") != null) {
					this.setOpaque(true);
					String colorString = obj.get("background-color").toString();
					try {
						// takes 2 characters each time to determine the RGB
						// color
						Color color = new Color(Integer.parseInt(colorString.substring(0, 2), 16), Integer.parseInt(colorString.substring(2, 4), 16),
								Integer.parseInt(colorString.substring(4, 6), 16));
						this.setBackground(color);

					} catch (NumberFormatException ex) {
						// illegal font-color is set in the input, the label
						// will use default text color: black
						Debug.log("illegal background-color format for high-lighted cell", Debug.ERROR_LEVEL);
					}
				} else {
					this.setOpaque(false);
					this.setBackground(new Color(0, 0, 0, 0));
				}
			} else {
				// default text if there is nothing to display
				this.setText("");
				// Debug.log("TransparentBackgroundRenderer:getTableCellRendererComponent(): null string was given in the input.",
				// Debug.WARNING_LEVEL);
			}
			return this;
		}
	}
}
